﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Timers;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Graphics;

	#endregion

	/// <summary>
	/// Třída, která definuje sprite
	/// </summary>
	public class Sprite : IDisposable
	{

		#region Const

		private const double DEFAULT_ANIMATION_SPEED = 50;

		#endregion

		#region Members

		private object m_animationLock = new object();

		private bool m_disposed;
		private Timer m_animationTimer;
		private string m_alias;
		private Texture2D m_texture;
		private Vector2 m_origin;

		private Rectangle m_rect;
		private int m_frameWidth;
		private int m_frameHeight;
		private int m_frameHorizontalCount;
		private int m_frameVerticalCount;
		private SpriteAntimation m_animation;
		private SpriteEffects m_effects;

		private int m_currentFrameHorizontalIndex;
		private int m_currentFrameVerticalIndex;

		private float m_scale = 1;
		private float m_scaleDo = 0;
		private float m_scaleMax = 0;
		private float m_scaleMin = 0;
		private int m_pulsateTimes = 0;
		private bool m_isPulsating;

		private float m_angle = 0;
		private float m_rotate = 0;
		private float m_rotateTo = float.MaxValue;
		private bool m_isRotating;

		#endregion

		#region Constructor

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="alias">Alias spritu</param>
		/// <param name="texture">Textura</param>
		/// <param name="frameHorizontalCount">Počet snímků v ose X</param>
		/// <param name="frameVerticalCount">Počet snímků v ose Y</param>
		public Sprite(string alias, Texture2D texture, int frameHorizontalCount, int frameVerticalCount)
		{
			this.m_animationTimer = new Timer();
			this.m_animationTimer.Interval = Sprite.DEFAULT_ANIMATION_SPEED;
			this.m_animationTimer.Elapsed += new ElapsedEventHandler(this.AnimationTimer_Elapsed);
			this.m_animationTimer.Enabled = false;

			this.m_alias = alias;
			this.m_texture = texture;
			this.m_frameWidth = this.m_texture.Width / frameHorizontalCount;
			this.m_frameHeight = this.m_texture.Height / frameVerticalCount;

			this.m_animation = SpriteAntimation.LeftToRightOnly;

			this.m_currentFrameHorizontalIndex =
				this.m_currentFrameVerticalIndex = 0;

			this.m_frameHorizontalCount = frameHorizontalCount;
			this.m_frameVerticalCount = frameVerticalCount;

			this.m_effects = SpriteEffects.None;
			this.m_angle = 0;

			this.m_origin = new Vector2(this.m_frameWidth / 2, this.m_frameHeight / 2);

			this.OnAnimation();
			if (this.m_frameHorizontalCount > 1)
				this.StartAnimation();
		}

		#endregion

		#region Events

		public event EventHandler RotationStart;

		public event EventHandler RotationStop;

		public event EventHandler PulsateStart;

		public event EventHandler PulsateStop;

		#endregion

		#region Methods

		protected void OnPulsateStop()
		{
			if (this.PulsateStop == null)
			{
				return;
			}
			this.PulsateStop(this, EventArgs.Empty);
		}

		protected void OnPulsateStart()
		{
			if (this.PulsateStart == null)
			{
				return;
			}
			this.PulsateStart(this, EventArgs.Empty);
		}

		protected void OnRotationStart()
		{
			if (this.RotationStart == null)
			{
				return;
			}
			this.RotationStart(this, EventArgs.Empty);
		}

		protected void OnRotationStop()
		{
			if (this.RotationStop == null)
			{
				return;
			}
			this.RotationStop(this, EventArgs.Empty);
		}


		public void Pulsate(float pulsateDiff, float max, float min, int times)
		{
			this.m_scaleDo = pulsateDiff;
			this.m_scaleMax = max;
			this.m_scaleMin = min;
			this.m_pulsateTimes = times;
			this.m_isPulsating = true;
			this.OnPulsateStart();
		}

		public void RotateStop()
		{
			this.m_isRotating = false;
			this.Rotate(0);
			this.OnRotationStop();
		}

		public void Rotate(float rotate)
		{
			this.m_rotate = rotate;
			this.m_rotateTo = float.MaxValue;
			this.m_isRotating = true;
			this.OnRotationStart();
		}

		public void RotateTo(float rotate, float to)
		{
			this.m_rotate = rotate;
			this.m_rotateTo = to;
			this.m_isRotating = true;
			this.OnRotationStart();
		}

		/// <summary>
		/// Metoda upraví přenosový RECT podle aktuálních hodnot
		/// </summary>
		private void OnAnimation()
		{
			if (this.m_rotate != 0)
			{
				this.m_angle += this.m_rotate;
				if (this.m_angle >= this.m_rotateTo)
				{
					this.m_isRotating = false;
					this.m_rotate = 0;
					this.m_angle = this.m_rotateTo;
					this.OnRotationStop();
				}
			}

			if (this.m_scaleDo != 0)
			{
				if (this.m_pulsateTimes <= 0)
				{
					this.m_scaleMax =
						this.m_scaleMin = 0;
					this.m_scale += (this.m_scale + this.m_scaleDo) > 1 ? -this.m_scaleDo : this.m_scaleDo;
					if (Math.Round(this.m_scale, 1, MidpointRounding.ToEven) == 1)
					{
						this.m_scaleDo = 0;
						this.m_isPulsating = false;
						this.OnPulsateStop();
					}
				}
				else
				{
					bool scalingUp = this.m_pulsateTimes % 2 == 0;
					if (scalingUp)
					{
						this.m_scale += this.m_scaleDo;
						if (this.m_scale >= this.m_scaleMax)
						{
							--this.m_pulsateTimes;
						}
					}
					else
					{
						this.m_scale -= this.m_scaleDo;
						if (this.m_scale <= this.m_scaleMin)
						{
							--this.m_pulsateTimes;
						}
					}
				}
			}

			lock (this.m_animationLock)
			{
				this.m_rect = new Rectangle(
					this.m_currentFrameHorizontalIndex * this.m_frameWidth,
					this.m_currentFrameVerticalIndex * this.m_frameHeight,
					this.m_frameWidth, this.m_frameHeight);
			}
		}

		public void StopAnimation()
		{
			if (this.m_animationTimer == null)
			{
				return;
			}
			this.m_animationTimer.Enabled = false;
		}

		/// <summary>
		/// Metoda spustí animaci
		/// </summary>
		public void StartAnimation()
		{
			if (this.m_animationTimer == null)
			{
				return;
			}
			this.m_animationTimer.Enabled = true;
		}

		/// <summary>
		/// Metoda pozastaví animaci
		/// </summary>
		public void PauseAnimation()
		{
			this.m_animationTimer.Enabled = false;
		}

		/// <summary>
		/// Metoda pozastaví animaci a nastaví konkrétní snímek
		/// </summary>
		/// <param name="horizontalIndex">Snímek v ose X</param>
		/// <param name="verticalIndex">Snímek v ose Y</param>
		public void PauseAnimation(int horizontalIndex, int verticalIndex)
		{
			this.m_animationTimer.Enabled = false;
			this.FrameHorizontalIndex = horizontalIndex;
			this.FrameVerticalIndex = verticalIndex;
		}

		/// <summary>
		/// Metoda vykreslí sprite
		/// </summary>
		/// <param name="batch"><see cref="SpriteBatch"/></param>
		/// <param name="position">Pozice spritu na obrazovce</param>
		public void Draw(SpriteBatch batch, Vector2 position)
		{
			batch.Draw(this.m_texture, Vector2.Add(position, this.m_origin), this.m_rect, Color.White,
				this.m_angle, this.m_origin, this.m_scale, this.m_effects, 1);
		}

		#endregion

		#region Properties

		public bool IsPulsating
		{
			get
			{
				return this.m_isPulsating;
			}
		}

		public bool IsRotating
		{
			get
			{
				return this.m_isRotating;
			}
		}

		/// <summary>
		/// Vrací šířku snímku
		/// </summary>
		public int FrameWidth
		{
			get
			{
				return this.m_frameWidth;
			}
		}

		/// <summary>
		/// Vrací výšku snímku
		/// </summary>
		public int FrameHeight
		{
			get
			{
				return this.m_frameHeight;
			}
		}

		/// <summary>
		/// Vrací celk. šířku
		/// </summary>
		public int Width
		{
			get
			{
				return this.m_texture.Width;
			}
		}

		/// <summary>
		/// Vrací celk. výšku
		/// </summary>
		public int Height
		{
			get
			{
				return this.m_texture.Height;
			}
		}

		/// <summary>
		/// Vrací alias spritu
		/// </summary>
		public string Alias
		{
			get
			{
				return this.m_alias;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje efekt
		/// </summary>
		public SpriteEffects Effects
		{
			get
			{
				return this.m_effects;
			}
			set
			{
				this.m_effects = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje úhel rotace
		/// </summary>
		public float Angle
		{
			get
			{
				return this.m_angle;
			}
			set
			{
				this.m_angle = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Nastaví nebo vrátí rychlost animace
		/// </summary>
		public double AnimationSpeed
		{
			get
			{
				return this.m_animationTimer.Interval;
			}
			set
			{
				this.m_animationTimer.Interval = value;
			}
		}

		/// <summary>
		/// Vrací přenosový rect
		/// </summary>
		public Rectangle Rectangle
		{
			get
			{
				lock (this.m_animationLock)
				{
					return this.m_rect;
				}
			}
		}
		
		/// <summary>
		/// Vrací texturu
		/// </summary>
		public Texture2D Texture
		{
			get
			{
				return this.m_texture;
			}
		}

		/// <summary>
		/// Vrací aktuální snímek v ose X
		/// </summary>
		public int FrameHorizontalIndex
		{
			get
			{
				return this.m_currentFrameHorizontalIndex;
			}
			set
			{
				this.m_currentFrameHorizontalIndex = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací aktuální snímek v ose Y
		/// </summary>
		public int FrameVerticalIndex
		{
			get
			{
				return this.m_currentFrameVerticalIndex;
			}
			set
			{
				this.m_currentFrameVerticalIndex = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje typ animace
		/// </summary>
		public SpriteAntimation Animation
		{
			get
			{
				return this.m_animation;
			}
			set
			{
				this.m_animation = value;
				this.OnAnimation();
			}
		}

		#endregion

		#region Event Handling

		private void AnimationTimer_Elapsed(object sender, ElapsedEventArgs e)
		{
			switch (this.m_animation)
			{
				case SpriteAntimation.LeftToRightOnly:
					++this.m_currentFrameHorizontalIndex;
					if (this.m_currentFrameHorizontalIndex >= this.m_frameHorizontalCount)
					{
						this.m_currentFrameHorizontalIndex = 0;
					}
					break;
			}

			this.OnAnimation();
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (this.m_disposed)
			{
				return;
			}
			this.m_animationTimer.Elapsed -= new ElapsedEventHandler(this.AnimationTimer_Elapsed);
			this.m_animationTimer.Enabled = false;

			this.m_disposed = true;
			GC.SuppressFinalize(this);
			GC.WaitForPendingFinalizers();
		}

		#endregion

	}
}
